import { xrplAccountToEvmAddress } from "../util/address-derivation";
import { BigNumber, ethers } from "ethers";
import { BridgeConfig } from "./index";
import { Log, LogStatus, LogType } from "../../util/Logger";
import Safe, { EthersAdapter, SafeFactory } from "@safe-global/protocol-kit";
import crypto from "crypto";
import { XChainBridge } from "xrpl/dist/npm/models/common";
import { BridgeDoorToken__factory } from "@peersyst/xrp-evm-contracts/dist/typechain/factories/BridgeDoorToken__factory";
import { BridgeChainProvider } from "./BridgeChainProvider";

export class EvmBridgeChainProvider implements BridgeChainProvider<undefined> {
    toEvmAddress(address: string): string {
        if (address.startsWith("0x")) return address;
        else return xrplAccountToEvmAddress(address);
    }

    defaultSafeContractNetworks(chainId: number) {
        return {
            [chainId.toString()]: {
                safeMasterCopyAddress: "0x3E5c63644E683549055b9Be8653de26E0B4CD36E",
                safeProxyFactoryAddress: "0xa6B71E26C5e0845f74c812102Ca7114b6a896AB2",
                multiSendAddress: "0xA238CBeb142c10Ef7Ad8442C6D1f9E89e07e7761",
                multiSendCallOnlyAddress: "0x40A2aCCbd92BCA938b02010E17A5b8929b49130D",
                fallbackHandlerAddress: "0xf48f2B2d2a534e402487b3ee7C18c33Aec0Fe5e4",
                signMessageLibAddress: "0x03F886722b44BefB13871D4a05621D38616D3b7c",
                createCallAddress: "0x7cbB62EaA69F79e6873cD1ecB2392971036cFAa4",
            },
        };
    }

    createBridgeWallet(): undefined {
        return undefined;
    }

    async fundAddress(fundingWallet: ethers.Wallet, address: string, amount: number): Promise<void> {
        const result = await fundingWallet.sendTransaction({
            from: fundingWallet.address,
            to: address,
            value: ethers.utils.parseEther(amount.toString()),
            gasLimit: 300_000,
        });
        await result.wait();
    }

    async setupAccounts(config: BridgeConfig, signer: ethers.Wallet, witnesses: string[]): Promise<string> {
        Log(LogType.Bridge, LogStatus.ToDo, "Funding witnesses...");
        for (const witness of witnesses) {
            Log(LogType.Bridge, LogStatus.Working, `Funding witness ${witness}...`);
            await this.fundAddress(signer, witness, 1);
        }
        Log(LogType.Bridge, LogStatus.Done, "Witnesses funded");

        Log(LogType.Bridge, LogStatus.ToDo, `Setting up safe smart contract...`);
        const safeAddress = await this.setupSafe(config, signer, witnesses);
        Log(LogType.Bridge, LogStatus.Done, `Safe smart contract set up at ${safeAddress}`);

        return safeAddress;
    }

    async setupSafe(config: BridgeConfig, signer: ethers.Wallet, witnesses: string[]): Promise<string> {
        const ethAdapter: any = new EthersAdapter({ ethers, signerOrProvider: signer });
        const safeFactory = await SafeFactory.create({
            ethAdapter,
            contractNetworks: this.defaultSafeContractNetworks(await signer.getChainId()),
        });
        const safe: Safe = await safeFactory.deploySafe({
            safeAccountConfig: {
                threshold: config.threshold,
                owners: witnesses,
            },
            saltNonce: BigNumber.from(crypto.randomInt(1_000_000)).toString(),
            options: {
                gasLimit: 300_000,
            },
        });
        return safe.getAddress();
    }

    async createIssuingChainBridge(config: BridgeConfig, lockingBridgeAddress: string): Promise<{ address: string; config: XChainBridge }> {
        const provider = new ethers.providers.JsonRpcProvider(config.issuingChain.url);
        const signerWallet = new ethers.Wallet(config.issuingChain.fundingPrivateKey, provider);

        const safeAddress = await this.setupAccounts(config, signerWallet, config.issuingChain.witnesses);

        Log(
            LogType.Bridge,
            LogStatus.ToDo,
            `Setting up bridge smart contract ${lockingBridgeAddress} ${config.lockingChain.tokenIssuer}...`,
        );
        const bridgeDoorTokenFactory = new BridgeDoorToken__factory(signerWallet);
        const bridgeDoorToken = await bridgeDoorTokenFactory.deploy(
            safeAddress,
            ethers.utils.parseEther(config.minRewardAmount.toString()),
            this.toEvmAddress(lockingBridgeAddress),
            this.toEvmAddress(config.lockingChain.tokenIssuer),
            ethers.constants.AddressZero,
            ethers.constants.AddressZero,
            config.lockingChain.tokenCode,
            `Bridged ${config.lockingChain.tokenCode} (${config.lockingChain.tokenIssuer})`,
        );
        Log(LogType.Bridge, LogStatus.Done, `Bridge smart contract set up at ${bridgeDoorToken.address}`);

        return {
            address: bridgeDoorToken.address,
            config: {
                LockingChainDoor: this.toEvmAddress(lockingBridgeAddress),
                LockingChainIssue: {
                    currency: config.lockingChain.tokenCode,
                    issuer: this.toEvmAddress(config.lockingChain.tokenIssuer),
                },
                IssuingChainDoor: bridgeDoorToken.address,
                IssuingChainIssue: {
                    currency: config.lockingChain.tokenCode,
                    issuer: bridgeDoorToken.address,
                },
            },
        };
    }

    async createLockingChainBridge(config: BridgeConfig, issuingBridgeAddress: string): Promise<{ address: string; config: XChainBridge }> {
        const provider = new ethers.providers.JsonRpcProvider(config.lockingChain.url);
        const signerWallet = new ethers.Wallet(config.lockingChain.fundingPrivateKey, provider);

        const safeAddress = await this.setupAccounts(config, signerWallet, config.lockingChain.witnesses);

        const bridgeDoorTokenFactory = new BridgeDoorToken__factory(signerWallet);
        const bridgeDoorToken = await bridgeDoorTokenFactory.deploy(
            safeAddress,
            ethers.utils.parseEther(config.minRewardAmount.toString()),
            ethers.constants.AddressZero,
            config.lockingChain.tokenIssuer,
            this.toEvmAddress(issuingBridgeAddress),
            this.toEvmAddress(issuingBridgeAddress),
            "",
            "",
        );

        return {
            address: bridgeDoorToken.address,
            config: {
                LockingChainDoor: bridgeDoorToken.address,
                LockingChainIssue: {
                    currency: config.lockingChain.tokenCode,
                    issuer: config.lockingChain.tokenIssuer,
                },
                IssuingChainDoor: this.toEvmAddress(issuingBridgeAddress),
                IssuingChainIssue: {
                    currency: config.lockingChain.tokenCode,
                    issuer: this.toEvmAddress(issuingBridgeAddress),
                },
            },
        };
    }
}
